/*
 * RT-Thread Secure
 *
 * Copyright (c) 2021, Shanghai Real-Thread Electronic Technology Co., Ltd.
 *
 * All rights reserved.
 */
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

struct rk3568_divider_pdata
{
    rt_ubase_t reg;
    rt_uint32_t shift;
    rt_uint32_t width;
    rt_bool_t onebased;
};
typedef struct rk3568_divider_pdata rk3568_divider_pdata_t;

static void set_rate(rt_clk_t *clk, rt_uint64_t parent_rate, rt_uint64_t rate)
{
    rk3568_divider_pdata_t *pdata = (rk3568_divider_pdata_t *)clk->pdata;
    rt_uint32_t mask = ((1 << (pdata->width)) - 1);
    rt_uint32_t div = 0;

    if(rate == 0)
    {
        rate = parent_rate;
    }

    div = parent_rate / rate;
    if(pdata->onebased)
    {
        div--;
    }

    if(div > mask)
    {
        div = mask;
    }

    clrsetreg(pdata->reg, mask << pdata->shift, div << pdata->shift);
}

static rt_uint64_t get_rate(rt_clk_t *clk, rt_uint64_t parent_rate)
{
    rk3568_divider_pdata_t *pdata = (rk3568_divider_pdata_t *)clk->pdata;
    rt_uint32_t mask = ((1 << (pdata->width)) - 1);
    rt_uint32_t div = 0;

    div = (readl(pdata->reg) >> pdata->shift);
    div &= mask;

    if(pdata->onebased)
    {
        div++;
    }

    if(div == 0)
    {
        div = 1;
    }

    return parent_rate / div;
}

static rt_uint64_t get_round_rate(rt_clk_t *clk, rt_uint64_t parent_rate, rt_uint64_t rate)
{
    return 0ULL;
}

static struct rt_clk_ops ops =
{
    .set_rate = set_rate,
    .get_rate = get_rate,
    .get_round_rate = get_round_rate,
};

rt_clk_t *rt_clk_rk3568_divider_register(const char *name, const char *parent, rt_ubase_t reg, rt_uint32_t shift,
    rt_uint32_t width, rt_bool_t onebased)
{
    rt_clk_t *clk = RT_NULL;
    rk3568_divider_pdata_t *pdata = RT_NULL;

    RT_ASSERT(name && parent);

    if(rt_strlen(name) == 0 || rt_strlen(parent) == 0)
    {
        clk = RT_NULL;
        goto _fail;
    }

    clk = rt_clk_create(name, &ops, 1, sizeof(rk3568_divider_pdata_t), 0);
    if(!clk)
    {
        goto _fail;
    }

    clk->parents[0] = rt_clk_find(parent);
    pdata = (rk3568_divider_pdata_t *)(clk->pdata);
#if defined(RT_USING_SMART)
    pdata->reg = (rt_ubase_t)rt_ioremap((void *)reg, 0x1000);
#else
    pdata->reg = reg;
#endif /* RT_USING_SMART */
    pdata->shift = shift;
    pdata->width = width;
    pdata->onebased = onebased;

    if(rt_clk_register(clk) != RT_EOK)
    {
        goto _fail;
    }

    return clk;

_fail:
    if(clk)
    {
        rt_free(clk);
    }

    return RT_NULL;
}

rt_err_t rt_clk_rk3568_divider_unregister(rt_clk_t *clk)
{
    rt_err_t ret = RT_EOK;

    ret = rt_clk_unregister(clk);
    if(!ret)
    {
        return ret;
    }

    rt_clk_delete(clk);

    return RT_EOK;
}

